/*-
 * Copyright (c) 2009 Jonathan Anderson
 * All rights reserved.
 *
 * WARNING: THIS IS EXPERIMENTAL SECURITY SOFTWARE THAT MUST NOT BE RELIED
 * ON IN PRODUCTION SYSTEMS.  IT WILL BREAK YOUR SOFTWARE IN NEW AND
 * UNEXPECTED WAYS.
 * 
 * This software was developed at the University of Cambridge Computer
 * Laboratory with support from a grant from Google, Inc. 
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/socket.h>
#include <sys/un.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <sysexits.h>

#include <libuserangel.h>

#include <map>

#include "Server.h"
#include "states/Accepted.h"

using namespace std;



Server::Server()
{
	serverFD = -1;
	shuttingDown = false;

	FD_ZERO(&sockets);
}



bool Server::bind(string path)
{
	shutdown();
	shuttingDown = false;

	socketName = path;


	struct sockaddr_un addr;
	addr.sun_family = AF_UNIX;
	strcpy(addr.sun_path, path.c_str());

	
	int fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
	if(fd < 0)
	{
		int olderrno = errno;
		shutdown();
		errno = olderrno;

		return false;
	}

	highestFD = fd;


	// No SIGPIPE, please
	int on = 1;
	if(setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on))) return false;

	// Re-use the angel address if the server has recently shut down
	if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) return false;



	if(::bind(fd, (struct sockaddr*) &addr, sizeof(struct sockaddr_un))
	   || ::listen(fd, 0))
	{
		int olderrno = errno;
		shutdown();
		errno = olderrno;

		return false;
	}

	serverFD = fd;
	FD_SET(fd, &sockets);


	return true;
}



int Server::accept()
{
	struct sockaddr_un clientaddr;
	unsigned int clientaddrlen;

	int sock = ::accept(serverFD, (struct sockaddr*) &clientaddr,
	                    &clientaddrlen);

	printf("Server:\t\tAccepted client %i\n", sock);

	if(sock < 0)
	{
		if(errno == EAGAIN) { usleep(1); return 0; }
		if(shuttingDown) return 0;

		perror("Error accepting client");
		return -1;
	}

	Client *client = new Client(sock);
	client->setState(Accepted::instance());

	if(!client->wakeup()) return 0;

	clients[sock] = client;
	FD_SET(sock, &sockets);
	if(sock > highestFD) highestFD = sock;


	return 0;
}



void Server::run()
{
	while(!shuttingDown)
	{
		struct fd_set selected;
		memcpy(&selected, &sockets, sizeof(sockets));
 

		int ready = select(highestFD + 1, &selected, NULL, NULL, NULL);
		if(ready < 0)
			// select() failed; GDB interrupt?
			continue;

		else if(ready == 0)
		{
			perror("select() timed out (this shoudn't happen)");
			return;
		}

		if(FD_ISSET(serverFD, &selected))
		{
			if(accept()) perror("Error accepting client");

			FD_CLR(serverFD, &selected);
			ready--;
		}

		typedef map<int,Client*>::iterator ClientIterator;
		for(ClientIterator c = clients.begin(); c != clients.end(); c++)
			if(FD_ISSET(c->second->socket(), &selected))
			{
				Client* client = c->second;
				int socket = client->socket();
				bool successful = client->wakeup();

				FD_CLR(socket, &selected);
				ready--;

				if(!successful)
				{
					delete client;
					clients.erase(socket);

					FD_CLR(socket, &sockets);

					if(socket == highestFD)
						while(!FD_ISSET(highestFD, &sockets)
						      && (highestFD >= 0))
							highestFD--;

					break;
				}
			}
	}
}



void Server::shutdown()
{
	shuttingDown = true;

	if(serverFD >= 0)
	{
		close(serverFD);
		serverFD = -1;

		if(socketName.length() > 0) unlink(socketName.c_str());
	}

	for(map<int,Client*>::iterator i = clients.begin(); i != clients.end(); i++)
		delete i->second;

	clients.clear();

	FD_ZERO(&sockets);
}

